///////////////////////////////////////////////////////////
// 使用V4L2接口捕获摄像头数据
// 版本 V2.0
// 最后修改日期 2024-08-11
// 测试环境为 泰山派( RK3566 ) Ubuntu20.04
//
// V2.0 
// 采用映射的方式直接读取内核中的图像数据
// 细化了读取方式
// 提供读取原始数据的方式
//
///////////////////////////////////////////////////////////

#ifndef _CAMER_V4L2_
#define _CAMER_V4L2_

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/videodev2.h>
#include <string.h>
#include <sys/mman.h>
#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <iostream>

#define REQBUFFER_COUNT 5 //申请内核空间的缓冲区的数量

typedef struct {
    unsigned char* data;
    unsigned int length;
}frame_uchar;

class Camer{
public:
    std::mutex mtx; // 互斥锁

    int fd, ret;
    struct v4l2_format vfmt; //摄像头参数
    struct v4l2_requestbuffers reqbuffer;
    struct v4l2_buffer mapbuffer;
    struct v4l2_buffer readbuffer;
    unsigned char *mptr[REQBUFFER_COUNT];//保存映射后用户空间的首地址
    unsigned int  size[REQBUFFER_COUNT];

    frame_uchar frame; // 存储每帧的地址和长度
    
   /*
    * 传入摄像头设备名称 例如 /dev/video9
    */
    Camer(const char *videoX);
    ~Camer();

   /*
    * 设置摄像头初始化参数
    * 在 Camer(const char *videoX) 有参构造函数中会自动调用一次
    */
    bool set_camer_parameter(struct v4l2_format & vfmt_p);

   /*
    * 申请内核空间
    */
    bool apply_for_kernel_space();

   /*
    * 开始采集 应该在初始化摄像头和申请缓冲区并完成内核空间映射后调用
    * 在 Camer(const char *videoX) 有参构造函数中会自动调用一次
    */
    bool turn_on_acquisition();

   /*
    * 结束采集
    */
    bool end_the_acquisition();

   /*
    * 获取图像数据并存放到成员变量 frame 中
    * 调用之前应该先初始化摄像头,并调用 turn_on_acquisition() 以开始采集
    * 函数内包含了xtract_frame_from_queue() 必须有对应的 put_it_back_queue() 调用以解锁互斥锁
    */
    bool get_rawdata_and_wait();

   /*
    * 从队列中提取一帧数据
    * 调用之前应该先初始化摄像头,并调用 turn_on_acquisition() 以开始采集
    * 使用了互斥锁上锁 必须有对应的 put_it_back_queue() 调用以解锁互斥锁
    */ 
    bool extract_frame_from_queue();

   /*
    * 放回队列
    * 表明该帧图像处理完毕, 开始采集下一帧前调用
    * 应该在 extract_frame_from_queue() 之后调用
    */
    bool put_it_back_queue();


   /*
    * 用于将图片数据的指针和图片数据长度装填到 frame_uchar 结构体中
    */
    bool refresh_frame_address(frame_uchar & container);
    
    

   /*
    * 获取图片
    */
    bool get_camer_and_save(std::string name = "my.jpg");
    bool get_frame_encode_to_opencv(cv::Mat& image);

private:
    

};

#endif